/*
    This file is part of Epoch <http://code.google.com/p/epoch/>.
    
    Copyright (C) 2011  Adam Meily <meily.adam@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include "ClientHandler.h"
#include <Logger.h>
#include "Chronos.h"

INIT_LOG;


ClientHandler::ClientHandler(int socket, const NodeConfig& config)
        : Protocol(socket, Protocol::ServerRole, config),
          m_state(WaitingForCert)//, m_chronos(Chronos::instance())
{
}

void ClientHandler::onResponse(const Response& response)
{
    DEBUG("response: " + response.operation());
    
    bool end = false;
    if(m_state == WaitingForCert) // auth
        end = (authenticate(response) < 0 || begin() < 0);
    else if(m_state == WaitingForListing)
    {
        if(listing(response) < 0) // bad listing
            end = (nextDir() == 1);
        else // good listing
            end = (nextFile() == 1 && nextDir() == 1);
    }
    else if(m_state == WaitingForDelta)
    {
        if(delta(response) < 0)
            end = (nextFile() == 1 && nextDir() == 1);
    }
    
    if(end)
        endSession();
}

int ClientHandler::authenticate(const Response& response)
{
    if(response.status() != Response::Ok)
    {
        INFO("auth: got bad response");
        return -1;
    }
    
    Certificate cert = Certificate::fromHex(response.args()["cert"]);
    /*
    if(cert.isNull())
    {
        DEBUG("auth: invalid cert");
        return -1;
    }
    
    if(cert.serverKey() != m_chronos->config()->key())
    {
        DEBUG("auth: server key mismatch");
        return -1;
    }
    
    RemoteDevice dev = m_chronos->store()->device(cert.deviceUuid());
    if(dev.isNull())
    {
        DEBUG("auth: unknown remote device");
        return -1;
    }
    
    if(dev.key() != cert.deviceKey())
    {
        DEBUG("auth: device key mismatch");
        return -1;
    }
    */
    
    m_device = Chronos::instance()->authenticate(cert);
    if(m_device.isNull())
        return -1;
    
    INFO("auth: hello " + m_device.uuid().toHex());
    
    return 0;
}

int ClientHandler::begin()
{
    QListIterator<Share> i(m_device.shares());
    while(i.hasNext())
    {
        //ShareDirectory dir(i.next(), "/");
        //m_directoryStack.push(dir);
        Share share = i.next();
        m_shareStack.push(share);
        
        if(m_chronos->prepare(share) < 0)
        {
            ERROR("prep: could not initialize share " + share.uuid().toHex());
            return -1;
        }
    }
    
    return nextDir();
}

int ClientHandler::endSession()
{
    if(m_remote.isNull())
        INFO("goodbye anonymous");
    else
        INFO("goodbye " + m_remote.uuid().toHex());
    
    closeConnection();
    return 0;
}


void ClientHandler::listing(const Response& response)
{
    QListIterator<Record> i = response.recordIterator();
    while(i.hasNext())
    {
        const Record& r = i.next();
        QString type = r["type"];
        
        if(type == "directory")
        {
            QString path = r["path"];
            if(!path.isEmpty())
                m_directoryStack.push(ShareDirectory(m_currentDir, path));
            else
                DEBUG("ls: invalid directory record");
        }
        else if(type == "file")
        {
            ShareFileRevision theirs = ShareFileRevision::fromRecord(m_currentDir, r, true);
            if(!theirs.isNull())
            {
                ShareFileRevision mine = m_chronos->store()->latestRevision(theirs.uuid());
                if(mine.isNull())
                {
                    DEBUG("ls: new file " + theirs.uuid().toHex());
                    m_fileStack.push(theirs);
                }
                else if(mine.lastModified() < theirs.lastModified() ||
                        theirs.size() != mine.size())
                {
                    ShareFileRevision next = mine.inc(theirs.size());
                    DEBUG("ls: new revision for " + next.uuid().toHex());
                    m_fileStack.push(next);
                }
            }
            else
                DEBUG("ls: invalid file record");
        }
        else
            DEBUG("ls: invalid record type");
    }
}

int ClientHandler::next()
{
    int r = nextFile();
    if(r == 1)
    {
        r = nextDir();
        if(r == 1)
        {
            r = nextShare();
            if(r == 0)
                r = nextDir();
        }
    }
    
    return r;
}


int ClientHandler::nextDir()
{
    if(m_directoryStack.isEmpty())
        return 1;
    
    m_currentDir = m_directoryStack.pop();
    
    m_state = WaitingForListing;
    Request req;
    req.method(Request::GetListing);
    req.addArg("path", m_currentDir);
    write(req);
    
    return 0;
}

int ClientHandler::nextFile()
{
    if(m_fileStack.isEmpty())
        return 1;
    
    m_currentFile = m_fileStack.pop();
    
    Request req;
    req.method(Request::GetDelta);
    
    QFile sig = m_currentFile.signature();
    bool sendSig = sig.exists() && sig.isReadable();
    if(sendSig && sig.open(QIODevice::ReadOnly))
    {
        req.addArg("size", QString::number(sig.size(), 10));
        req.addArg("mode", "sig");
    }
    else
    {
        if(sendSig)
        {
            INFO("sig: could not open sig file for " +
                 m_currentFile.uuid().toHex() + "; reason: " +
                 sig.errorString());
        }
        sendSig = false;
        req.addArg("mode", "no-sig");
    }
    write(req);
    
    if(sendSig)
    {
        while(!sig.atEnd())
            write(sig.read(1024));
        sig.close();
    }
    
    m_rawBytesReceived = 0;
    return 0;
}

int ClientHandler::nextShare()
{
    if(m_shareStack.isEmpty())
        return 1;
    
    Share share = m_shareStack.pop();
    m_directoryStack.push(share.path());
    return 0;
}


int ClientHandler::delta(const Response& response)
{
    if(response.status() != Response::Ok)
    {
        INFO("delta: got bad response");
        return -1;
    }
    
    Record args = response.args();
    bool ok;
    quint64 size = args["size"].toULongLong(&ok, 10);
    if(!ok)
    {
        DEBUG("delta: no size given");
        return -1;
    }
    
    if(size == 0)
        return 1;
    
    m_rawBytesNeeded = size;
    stopMessagePreprocess();
    return 0;
}



void ClientHandler::onDisconnect()
{
    DEBUG("disconnected");
}


void ClientHandler::onRawMessage(const QByteArray& raw)
{
    m_rawBytesReceived += raw.length();
    // m_delta.write(raw);
    if(m_rawBytesReceived == m_currentFile.size())
    {
        m_rawStream.close();
        resumeMessagePreprocess();
        deltaDone();
    }
}

void ClientHandler::deltaDone()
{
    m_rawStream.close();
    m_chronos->store()->save(m_currentFile);
    m_chronos->scheduler()->newRevision(m_currentFile);
    
    if(next() == 1)
        endSession();
}


void ClientHandler::onHandshakeComplete()
{
    m_state = WaitingForCert;
    Request req;
    req.method(Request::GetCert);
    write(req);
}

